#pragma once
#include <leveldb/db.h>

#include <memory>
#include <string>
#include <iostream>

#include "logutils.hpp"

class KVWrapper {
 public:
  static KVWrapper* GetInstance(std::string name) {
    if (name == "user") {
      static KVWrapper kvWrapper_user("user");
      return &kvWrapper_user;
    }
    if (name == "warehouse") {
      static KVWrapper kvWrapper_warehouse("warehouse");
      return &kvWrapper_warehouse;
    }
    if (name == "test") {
      static KVWrapper kvWrapper_user("test");
      return &kvWrapper_user;
    }
  }

  void Init() {
    leveldb::DB* db;
    leveldb::Options options;
    options.create_if_missing = true;
    auto status = leveldb::DB::Open(options, base_path_ + name_, &db);
    hassert(status.ok(), status.ToString().c_str());
    kvHandler_.reset(db);
  }

  void Put(const std::string& key, const std::string& value) {
    hassert(kvHandler_.get() != nullptr, "kvHandler_ is nullptr");

    std::cout << name_ << ": Put (" << key << ", " << value << ")" << std::endl;

    auto status = kvHandler_->Put(leveldb::WriteOptions(), key, value);
    hassert(status.ok(), status.ToString().c_str());
  }

  std::string Get(const std::string& key) {
    hassert(kvHandler_.get() != nullptr, "kvHandler_ is nullptr");

    std::string value;
    auto status = kvHandler_->Get(leveldb::ReadOptions(), key, &value);
    std::cout << name_ << ": Get (" << key << ", " << value << ")" << std::endl;
    hassert(status.ok(), status.ToString().c_str());
    return value;
  }

  std::unique_ptr<std::vector<std::pair<std::string, std::string>>> PrefixGet(
      const std::string& prefix) {
    auto result =
        std::make_unique<std::vector<std::pair<std::string, std::string>>>();
    auto it = kvHandler_->NewIterator(leveldb::ReadOptions());
    for (it->Seek(prefix); it->Valid(); it->Next()) {
      std::cout << it->key().ToString() << std::endl;
      result->push_back({it->key().ToString(), it->value().ToString()});
    }
    return result;
  }

  bool Exist(const std::string& key) {
    hassert(kvHandler_.get() != nullptr, "kvHandler_ is nullptr");
    std::string _;
    auto status = kvHandler_->Get(leveldb::ReadOptions(), key, &_);
    hassert(status.ok() || status.IsNotFound(), status.ToString().c_str());
    return status.ok();
  }

  bool PutIfMissing(const std::string& key, const std::string& value) {
    if (this->Exist(key)) {
      return false;
    }
    this->Put(key, value);
    return true;
  }

  void Add(const std::string& key, int val) {
    if (!this->Exist(key)) {
      if (val > 0) {
        this->Put(key, std::to_string(val));
      }
    } else {
      auto pre_val = std::stoi(this->Get(key));
      if (pre_val + val <= 0) {
        kvHandler_->Delete(leveldb::WriteOptions(), key);
      } else {
        this->Put(key, std::to_string(pre_val + val));
      }
    }
  }

  std::unique_ptr<std::vector<std::pair<std::string, std::string>>> GetAll() {
    auto result =
        std::make_unique<std::vector<std::pair<std::string, std::string>>>();
    auto it = kvHandler_->NewIterator(leveldb::ReadOptions());
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
      result->push_back({it->key().ToString(), it->value().ToString()});
    }
    return result;
  }

  void Iterate() {
    auto it = kvHandler_->NewIterator(leveldb::ReadOptions());
    std::cout << "--- user ---" << std::endl;
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
      std::cout << it->key().ToString() << ": " << it->value().ToString()
                << std::endl;
    }
    std::cout << std::endl;
    assert(it->status().ok());
  }

 private:
  KVWrapper(const std::string& name) { name_ = name; };

  std::unique_ptr<leveldb::DB> kvHandler_;

  const std::string base_path_ = "/tmp/kv_";

  std::string name_;
};
